/**
 * Copyright 1998-2006, CHISEL Group, University of Victoria, Victoria, BC, Canada.
 * All rights reserved.
 */
package ca.uvic.cs.cogz.mapping.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ContainerAdapter;
import java.awt.event.ContainerEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;

import ca.uvic.cs.cogz.PromptUIPlugin;
import ca.uvic.cs.cogz.mapping.core.AbstractMapElement;
import ca.uvic.cs.cogz.mapping.core.CandidateMapElement;
import ca.uvic.cs.cogz.mapping.core.MappingCollection;
import ca.uvic.cs.cogz.mapping.core.TemporaryMapElement;
import ca.uvic.cs.cogz.mapping.core.VerifiedMapElement;
import ca.uvic.cs.cogz.mapping.core.MappingTypeManager.MappingType;
import ca.uvic.cs.cogz.mapping.filter.CandidationOperationFilter;
import ca.uvic.cs.cogz.mapping.filter.TreeFilterManager;
import ca.uvic.cs.cogz.mapping.ontology.DataLoaderFacade;
import ca.uvic.cs.cogz.mapping.ui.renderer.ArcRenderer;
import ca.uvic.cs.cogz.mapping.ui.tree.MappingJTree;
import ca.uvic.cs.cogz.mapping.ui.tree.ZoomManager;
import ca.uvic.cs.cogz.reports.AbstractReport;
import ca.uvic.cs.cogz.stats.StatsManager;
import ca.uvic.cs.cogz.util.BrowserLaunch;
import ca.uvic.cs.cogz.util.DropDownButton;
import ca.uvic.cs.cogz.util.IconConstants;
import ca.uvic.cs.cogz.util.PropertiesManager;
import ca.uvic.cs.cogz.util.TreeUtil;
import ca.uvic.cs.cogz.util.PropertiesManager.MappingVisualizationProperties;
import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.Frame;
import edu.stanford.smi.protege.model.KnowledgeBase;
import edu.stanford.smi.protege.model.Slot;
import edu.stanford.smi.protege.ui.ParentChildNode;
import edu.stanford.smi.protege.ui.ProjectManager;
import edu.stanford.smi.protege.util.LazyTreeNode;
import edu.stanford.smi.protegex.prompt.Mappings;
import edu.stanford.smi.protegex.prompt.operation.MergeFramesOperation;
import edu.stanford.smi.protegex.prompt.operation.Operation;
import edu.stanford.smi.protegex.prompt.operation.editor.MergeSlotsOperationEditor;
import edu.stanford.smi.protegex.prompt.plugin.SelectionListener;
import edu.stanford.smi.protegex.prompt.plugin.util.SelectableContainer;
import edu.stanford.smi.protegex.prompt.plugin.util.mapping.MapPluginFacade;
import edu.stanford.smi.protegex.prompt.ui.ActionListPane;
import edu.stanford.smi.protegex.prompt.ui.CreateNewOperationPane;
import edu.stanford.smi.protegex.prompt.ui.ListSplitPane;
import edu.stanford.smi.protegex.prompt.ui.SuggestionListPane;
import edu.stanford.smi.protegex.prompt.ui.TabComponent;

/**
 * Main GUI for performing and visualizing mappings.
 * 
 * @author Chris Callendar, Sean Falconer
 * @date 14-Nov-06
 */
public class MappingVisualizationPanel extends JPanel implements TreeSelectionListener {
	private static final String SHOW_ONLY_EXACT_NAME_MATCHES = "Show only exact name matches";
	private static final String FILTER_BACKGROUND_ITEMS_ON_SELECTION = "Filter background mappings on selection";
	private static final String SHOW_ONLY_CURRENT_SELECTIONS_MAPPED_ITEMS = "Filter trees based on mappings";
	private static final String SHOW_ONLY_TEMPORARY_ITEMS = "Show only temporary items";
	private static final String SHOW_ONLY_CANDIDATE_ITEMS = "Show only candidate items";
	private static final String SHOW_TEMPORARY_LABEL = "Show temporary";
	private static final String SHOW_CANDIDATES_LABEL = "Show candidates";
	private static final String SHOW_MAPPED_LABEL = "Show mapped";
	private static final String MARK_AS_MAPPED_LABEL = "Mark as mapped";
	private static final String MARK_AS_TEMPORARY_LABEL = "Mark as temporary";
	private static final String REMOVE_MAPPING_LABEL = "Remove mapping";
	private static final String ADD_MAPPING_LABEL = "Add mapping";
	private static final String ADD_TEMPORARY_MAPPING_LABEL = "Add temporary mapping";
	private static final String SHOW_ONLY_MAPPED_ITEMS_LABEL = "Show only mapped items";
	private static final String PREVIOUS_MAPPING_LABEL = "Previous mapping";
	private static final String NEXT_MAPPING_LABEL = "Next mapping";
	private static final String FISH_EYE_ZOOM_LABEL = "Fish-eye zoom";
	private static final String SYNCHRONIZE_ZOOM_LABEL = "Synchronize zoom";
	private static final String MAPPING_TYPE_FILTER_LABEL = "Mapping type filter";
	private static final String SHOW_SLOTS = "Show slots";

	/** search text that is displayed before the user types */
	public static final String SEARCH_DEFAULT_TEXT_LABEL = "type filter text";

	private static final long serialVersionUID = 3717788935818629523L;

	/** the y-coordinate location for the top of the main panel */
	private static final int MAIN_PANEL_LOCATION = 32;

	/** the owner frame of this panel */
	private JFrame parentFrame;

	/** panel for stacking all the various mapping component panels */
	private JLayeredPane layeredPane;

	/** the main panel contains the source and target trees, bottom most panel */
	private JPanel mainPanel;

	/** panel for drawing tooltips, top most panel */
	private JPanel tooltipsPanel;

	/**
	 * panel that contains the source and target neighborhood visualizations
	 */
	private NeighborhoodControllerPanel neighborhoodPanel;
	
	/**
	 * panel that displays properties for an instantiated cls or mapped cls
	 */
	private ClsPropertyPanel clsPropertyPanel;
	
	/**
	 * panel that displays the ATL rules generated so far
	 */
	private ATLRulePanel atlRulePanel;

	/** tab panel containing the neighborhood view */
	private JTabbedPane bottomPanel;

	/**
	 * split panel that separates the bottom panel (neighborhood view) from the arc and ontology
	 * panels
	 */
	private JSplitPane verticalSplitPane;

	/** the spanes for the source and target trees and mapping arcs panel */
	private JSplitPane mainSplitPane;
	private JSplitPane innerSplitPane;

	/**
	 * panel for containing the main toolbar, stacks the arcs panel to prevent arcs from drawing
	 * outside the tree bounds
	 */
	private JPanel toolbarPanel;

	/** middle panel for the mainPanel */
	private JPanel arcsMiddleHoverPanel;

	/** middle panel for the toolbarPanel */
	private JPanel middlePanel;

	/** central panel that contains the arc view and suggestion list */
	private JTabbedPane centralPanel;

	/** panel to contain Prompt's suggestion list panel */
	private JPanel suggestionListPanel;
	
	/** panel to contain the mappings completed by the user */
	private CompletedMappingsPane completedMappingsPanel;

	/** the source and target mapping progress panels */
	private MappingProgressPanel sourceMappingProgressPanel;
	private MappingProgressPanel targetMappingProgressPanel;

	/** MappingJTree components for the source and target ontologies */
	private MappingJTree sourceTree;
	private MappingJTree targetTree;

	/** labels for the source and target ontologies */
	private JLabel sourceTitle;
	private JLabel targetTitle;

	/** sliders for controlling tree zooms */
	private JSlider sourceSlider;
	private JSlider targetSlider;

	/** menu stuff */
	private JMenuBar menuBar;
	private JMenu cogzMenu;
	private JCheckBoxMenuItem semanticZoomMenuItem;
	private JCheckBoxMenuItem syncZoomMenuItem;
	private JMenuItem filterControlMenuItem;
	private JCheckBoxMenuItem exactNameFilterMenuItem;
	private JCheckBoxMenuItem filterBasedOnMappingsMenuItem;
	private JCheckBoxMenuItem filterBackgroundItemsMenuItem;

	/** flag for indicating whether the zoom levels should be synchronized between trees */
	private boolean zoomSync;

	/** custom panel for displaying a tooltip */
	private BalloonPanel tooltip;

	/** search textboxes located above the ontology trees */
	private JTextField sourceSearchText;
	private JTextField targetSearchText;
	
	/** reference to Prompt's suggestion list pane */
	private ActionListPane suggestionListPane;

	private boolean ignoreSelection = false;
	
	/** flag for ignoring the suggestion selection event */
	private boolean ignoreSuggestionSelection = false;

	/** true if we want to filter trees based on selections and existing mappings */
	private boolean filterToMappedItems;

	/** object containing all the mapped elements */
	private MappingCollection mappingCollection;

	/** reference to the dialog for controlling what mapping lines should be displayed */
	private MappingTypeFilterDialog mappingTypeFilterDialog;

	/** the currently selected mapping path for source and target trees */
	private Collection<Frame> currentSourcePath = null;
	private Collection<Frame> currentTargetPath = null;

	/** the currently selected map element */
	private List<AbstractMapElement> currentMapElement = null;

	/** reference to the class that renders curves between mapped items */
	private ArcRenderer arcRenderer;

	/** toolbar actions */
	private Action addMappingAction;
	private Action removeMappingAction;
	private Action approveMappingAction;
	private Action temporaryMappingAction;
	private Action addTemporaryMappingAction;
	private Action showMappingsAction;
	private Action sourceMappingMoveUpAction;
	private Action sourceMappingMoveDownAction;
	private Action targetMappingMoveUpAction;
	private Action targetMappingMoveDownAction;

	private JToggleButton showOnlyExactMatchesAction;

	/**
	 * toggle button for turning on filter that only shows the hierarchy necessary to show mapping
	 * items
	 */
	private JToggleButton filterToMappedItemsAction;

	/** toggle button for turning on filter that removes background mapping lines */
	private JToggleButton filterBackgroundItemsAction;

	/** filter drop down button for the source tree */
	private DropDownButton sourceFilterButton;
	private JMenu sourceFilterMenu;

	/** filter drop down button for the target tree */
	private DropDownButton targetFilterButton;
	private JMenu targetFilterMenu;

	private JToggleButton sourceFilterItemsAction;
	private JToggleButton targetFilterItemsAction;
	
	private JCheckBoxMenuItem showSlotsMenuItem;

	/** source drop-down filter menu items */
	private JCheckBoxMenuItem sourceMappedMenuItem;
	private JCheckBoxMenuItem sourceCandidateMenuItem;
	private JCheckBoxMenuItem sourceTemporaryMenuItem;

	/** target drop-down filter menu items */
	private JCheckBoxMenuItem targetMappedMenuItem;
	private JCheckBoxMenuItem targetCandidateMenuItem;
	private JCheckBoxMenuItem targetTemporaryMenuItem;

	/** reference to the source and target knowledge bases */
	private KnowledgeBase sourceKb;
	private KnowledgeBase targetKb;

	/** reference to the source and target statistical managers */
	private StatsManager sourceStatsManager;
	private StatsManager targetStatsManager;

	/** the current mapping type that is being used to filter the view */
	private MappingType currentSourceFilterType;
	private MappingType currentTargetFilterType;

	/** used to restore the divider location when an extracted dialog is put back into the view */
	private int previousDividerLocation;

	/** popup menu for right-clicking on the bottom tab pane */
	private JPopupMenu tabPopupMenu;

	private MappingPanelMouseListener mappingMouseListener;

	private int numOfRuns;

	public MappingVisualizationPanel(JFrame parentFrame) throws HeadlessException {
		this(parentFrame, null, null);
	}

	public MappingVisualizationPanel(JFrame parentFrame, StatsManager sourceStatsManager, StatsManager targetStatsManager) throws HeadlessException {
		super(new BorderLayout(), true);

		this.parentFrame = parentFrame;
		this.sourceStatsManager = sourceStatsManager;
		this.targetStatsManager = targetStatsManager;

		// initialize private member objects
		this.mappingCollection = DataLoaderFacade.getInstance().getMappingCollection();

		this.arcRenderer = new ArcRenderer(mappingCollection);
		this.sourceKb = DataLoaderFacade.getInstance().getSourceOntology();
		this.targetKb = DataLoaderFacade.getInstance().getTargetOntology();
		
		this.currentMapElement = new ArrayList<AbstractMapElement>();

		// set up our TreeUI so that no lines are drawn
		UIManager.put("Tree.paintLines", "false");

		initListeners();
		createActions();
		initialize();
		getSourceTree().requestFocus();

		// initialize trees
		getSourceTree().initTree();
		getTargetTree().initTree();
	}

	public void dispose() {
		menuBar.remove(cogzMenu);
		menuBar.invalidate();
		menuBar.validate();
		menuBar.repaint();
	}

	/**
	 * Refreshes the panels, trees, and existing dialogs.
	 */
	public void refresh() {
		currentSourcePath = null;
		currentTargetPath = null;

		arcRenderer.reset();
		TreeUtil.resetCachedObjects();
		
		repaintArcsPanel();
		refreshTrees();
		sourceMappingProgressPanel.repaint();
		targetMappingProgressPanel.repaint();
		neighborhoodPanel.repaint();
		
		completedMappingsPanel.refresh();

		currentMapElement.clear();
		
		if(getCurrentMapElement() != null) {
			currentMapElement.add(getCurrentMapElement());
		}
		updateActions();
		
		if(atlRulePanel != null) {
			atlRulePanel.updateRules();
		}
	}

	public void repaintArcsPanel() {
		if (mappingCollection.getSize() > 0 || mappingMouseListener.isDragging()) {
			getArcsPanel().repaint();
		}
	}

	/**
	 * Activated when the source or target ontology trees change values.
	 */
	public void valueChanged(TreeSelectionEvent e) {
		if (!ignoreSelection) {
			try {
				ignoreSelection = true;

				if (!mappingMouseListener.isDragging()) {
					//if (!e.isAddedPath()) {
					//	currentMapElement.clear();
					//	currentMapElement.add(getCurrentMapElement());
					//}
					
					selectTreeConcept((JTree) e.getSource(), e.getPath());
				}

				refreshTrees();
				updateActions();
			} finally {
				ignoreSelection = false;
			}
		}
	}
	
	public void selectTreeConcept(JTree tree, TreePath path) {
		Frame frame = TreeUtil.getLastSelectedUserObject(path);
		
		AbstractMapElement mapElement = getCurrentMapElement();
		
		if(frame == null) return;
		// || (mapElement != null && mapElement.getSourceFrame().equals(frame))
		
		MappingJTree oppositeTree = null;
		if (path != null && tree.equals(sourceTree)) {
			oppositeTree = targetTree;
		} else if (path != null && tree.equals(targetTree)) {
			oppositeTree = sourceTree;
		}
		
		if (TreeUtil.pathContains(tree.getSelectionPaths(), path)) {
			updateTreeSelection(path, oppositeTree, null);
		}
		else {
			removeMappingElementSelection(path, oppositeTree, frame);
		}
	}

	/**
	 * Toggles the source ontology filter for the given mappingType.
	 * 
	 * @param mappingType
	 */
	public void applyMappingSourceFilter(MappingType mappingType) {
		Icon icon = null;
		if (mappingType == MappingType.VERIFIED_MAPPING) {
			icon = sourceFilterButton.getMenu().getItem(0).getIcon();
			sourceFilterItemsAction.setToolTipText(SHOW_ONLY_MAPPED_ITEMS_LABEL);
		} else if (mappingType == MappingType.CANDIDATE_MAPPING) {
			icon = sourceFilterButton.getMenu().getItem(1).getIcon();
			sourceFilterItemsAction.setToolTipText(SHOW_ONLY_CANDIDATE_ITEMS);
		} else if (mappingType == MappingType.TEMPORARY_MAPPING) {
			icon = sourceFilterButton.getMenu().getItem(2).getIcon();
			sourceFilterItemsAction.setToolTipText(SHOW_ONLY_TEMPORARY_ITEMS);
		}
		((AbstractAction) sourceFilterItemsAction.getAction()).putValue(Action.SMALL_ICON, icon);

		sourceFilterItemsAction.setSelected(!sourceFilterItemsAction.isSelected());
		filterSourceMappings(mappingType);
	}

	/**
	 * Toggles the target ontology filter for the given mappingType.
	 * 
	 * @param mappingType
	 */
	public void applyMappingTargetFilter(MappingType mappingType) {
		Icon icon = null;
		if (mappingType == MappingType.VERIFIED_MAPPING) {
			icon = targetFilterButton.getMenu().getItem(0).getIcon();
			targetFilterItemsAction.setToolTipText(SHOW_ONLY_MAPPED_ITEMS_LABEL);
		} else if (mappingType == MappingType.CANDIDATE_MAPPING) {
			icon = targetFilterButton.getMenu().getItem(1).getIcon();
			targetFilterItemsAction.setToolTipText(SHOW_ONLY_CANDIDATE_ITEMS);
		} else if (mappingType == MappingType.TEMPORARY_MAPPING) {
			icon = targetFilterButton.getMenu().getItem(2).getIcon();
			targetFilterItemsAction.setToolTipText(SHOW_ONLY_TEMPORARY_ITEMS);
		}
		((AbstractAction) targetFilterItemsAction.getAction()).putValue(Action.SMALL_ICON, icon);

		targetFilterItemsAction.setSelected(!targetFilterItemsAction.isSelected());
		filterTargetMappings(mappingType);
	}

	/**
	 * Uses the TreeSearchManager object to perform the search against the ontology and uses the
	 * returned results to update the given JTree object.
	 */
	public void performSearch(String searchText, MappingJTree tree, KnowledgeBase kb) {
		Collection<Collection> results = TreeFilterManager.getInstance().performSearch(kb, searchText);

		if (results == null && searchText.length() > 0) {
			return;
		}

		// collapse currently expanded paths
		Enumeration<TreePath> paths = tree.getExpandedDescendants(new TreePath(tree.getModel().getRoot()));
		while ((paths != null) && paths.hasMoreElements()) {
			TreePath path = paths.nextElement();
			tree.collapsePath(path);
		}
		
		tree.initTree(results, searchText.length() > 0 && !searchText.equals("*"));
		tree.refreshTree();
	}

	/**
	 * Update the tree sizes.
	 */
	private void refreshTrees() {
		sourceTree.refreshTree();
		targetTree.refreshTree();
	}

	/**
	 * Gets the currently selected map element based on what is selected in the source and target
	 * trees.
	 * 
	 * @return
	 */
	private AbstractMapElement getCurrentMapElement() {
		// get the source and target classes
		Object o1 = TreeUtil.getLastSelectedUserObject(sourceTree);
		Object o2 = TreeUtil.getLastSelectedUserObject(targetTree);
		
		if(o1 instanceof Frame && o2 instanceof Frame) {
			return mappingCollection.getMapElement((Frame)o1, (Frame)o2);
		}
		
		return null;
	}
	
	private void handleCompletedMappingSelection() {
		if(!ignoreSuggestionSelection) {
			AbstractMapElement mapElement = completedMappingsPanel.getSelectMapElement();
			if(mapElement != null) {
				selectFramesInTrees(mapElement.getSourceFrame(), mapElement.getTargetFrame());
			}
		}
		ignoreSuggestionSelection = false;
	}
	
	private void handleSuggestionSelection(Object selectedItem) {
		if(!ignoreSuggestionSelection) {
			Operation operation = (Operation) selectedItem;
			Frame srcFrame = null, targetFrame = null;
			if (operation.getArgs().getArg(0) instanceof Frame) {
				srcFrame = (Frame) operation.getArgs().getArg(0);
				
				if(sourceKb.getFrame(srcFrame.getFrameID()) == null) {
					srcFrame = Mappings.getSingleSource(srcFrame);
				}
			}
			if (operation.getArgs().getArg(1) instanceof Frame) {
				targetFrame = (Frame) operation.getArgs().getArg(1);
				
				if(targetKb.getFrame(targetFrame.getFrameID()) == null) {
					targetFrame = Mappings.getSingleSource(targetFrame);
				}
			}

			selectFramesInTrees(srcFrame, targetFrame);
		}
		ignoreSuggestionSelection = false;
	}

	/**
	 * Selects the mapping arc and corresponding JTree nodes
	 */
	protected void selectMappingArc(Component source, Point p, boolean isCtrlDown) {
		Point selectionPoint = SwingUtilities.convertPoint(source, p, getArcsPanel());
		ArrayList<AbstractMapElement> mapElements = new ArrayList<AbstractMapElement>();
		
		List<AbstractMapElement> selectedMapElements = arcRenderer.getMapElement(selectionPoint);
		if (selectedMapElements != null) {
			if (!arcRenderer.isExpanded(selectedMapElements)) {
				if (selectedMapElements.size() > 1) {
					arcRenderer.addToExpansionList(selectedMapElements);
					arcRenderer.getArcsPanel().repaint();
				}
				if(!isCtrlDown) currentMapElement.clear();
				currentMapElement.addAll(selectedMapElements);
				mapElements.addAll(selectedMapElements);
			} else if (selectedMapElements.size() > 1) {
				arcRenderer.removeFromExpansionList(selectedMapElements);
				arcRenderer.getArcsPanel().repaint();
			} else if (selectedMapElements.size() == 1) {
				if(!isCtrlDown) currentMapElement.clear();
				currentMapElement.addAll(selectedMapElements);
				mapElements.addAll(selectedMapElements);
			}
		}

		if (mapElements.size() >= 1) {
			ignoreSelection = true;

			// select source and target paths
			selectTreePath(mapElements, getSourceTree(), !isCtrlDown);
			selectTreePath(mapElements, getTargetTree(), !isCtrlDown);

			ignoreSelection = false;
		}

		updateActions();
	}
	
	/**
	 * Called to remove mapping elements from the tree.  This occurs when a tree element is deselected.  We
	 * have to deselect the corresponding elements in the opposite tree.
	 */
	private void removeMappingElementSelection(TreePath path, MappingJTree tree, Frame focalFrame) {
		ArrayList<AbstractMapElement> mapElements = (ArrayList<AbstractMapElement>) mappingCollection.getMappedElements(focalFrame);
		tree.removeTreePath(mapElements);
		
		currentMapElement.removeAll(mapElements);
	}

	/**
	 * Updates the tree selection by highlighting any existing mapping and selecting the
	 * corresponding mapped components and also updates the treeUI size to handle scrollbar
	 * updating.
	 */
	private void updateTreeSelection(TreePath path, MappingJTree tree, Frame focalFrame) {
		Frame selectedFrame = TreeUtil.getLastSelectedUserObject(path);

		ArrayList<AbstractMapElement> mapElements;
		if (focalFrame == null) {
			mapElements = (ArrayList<AbstractMapElement>) mappingCollection.getMappedElements(selectedFrame);
		} else {
			mapElements = mappingCollection.getMappedElements(path, focalFrame, !tree.isSource());
		}

		if (filterToMappedItems && path != null) {
			Frame frame = (Frame) ((ParentChildNode) path.getLastPathComponent()).getUserObject();
			filterToMappedItems(mapElements, tree, frame);
		}

		// do not overwrite the tree selection if the opposite tree has multiple items selected 
		if((tree == sourceTree && targetTree.getSelectionCount() > 1) || (tree == targetTree && sourceTree.getSelectionCount() > 1)) {
			selectTreePath(mapElements, tree, false);
		}
		else {
			selectTreePath(mapElements, tree, true);
			currentMapElement.clear();
		}
		
		if(mapElements != null) {
			currentMapElement.addAll(mapElements);
		}

		if (tree.getZoomManager().isSemanticZoom()) {
			repaintArcsPanel();
			tree.getZoomTreeUI().updateSize();
		}
	}

	private void filterSourceMappings(MappingType mappingType) {
		TreeFilterManager.getInstance().setActiveSearch(TreeFilterManager.SOURCE_SEARCH);
		filterTreeOnMappingType(getSourceTree(), sourceKb, sourceFilterItemsAction, mappingType);
		this.currentSourceFilterType = mappingType;

		sourceCandidateMenuItem.setSelected(false);
		sourceMappedMenuItem.setSelected(false);
		sourceTemporaryMenuItem.setSelected(false);
		if (mappingType == MappingType.CANDIDATE_MAPPING) {
			sourceCandidateMenuItem.setSelected(sourceFilterItemsAction.isSelected());
		} else if (mappingType == MappingType.VERIFIED_MAPPING) {
			sourceMappedMenuItem.setSelected(sourceFilterItemsAction.isSelected());
		} else if (mappingType == MappingType.TEMPORARY_MAPPING) {
			sourceTemporaryMenuItem.setSelected(sourceFilterItemsAction.isSelected());
		}
	}

	private void filterTargetMappings(MappingType mappingType) {
		TreeFilterManager.getInstance().setActiveSearch(TreeFilterManager.TARGET_SEARCH);
		filterTreeOnMappingType(getTargetTree(), targetKb, targetFilterItemsAction, mappingType);
		this.currentTargetFilterType = mappingType;

		targetCandidateMenuItem.setSelected(false);
		targetMappedMenuItem.setSelected(false);
		targetTemporaryMenuItem.setSelected(false);
		if (mappingType == MappingType.CANDIDATE_MAPPING) {
			targetCandidateMenuItem.setSelected(targetFilterItemsAction.isSelected());
		} else if (mappingType == MappingType.VERIFIED_MAPPING) {
			targetMappedMenuItem.setSelected(targetFilterItemsAction.isSelected());
		} else if (mappingType == MappingType.TEMPORARY_MAPPING) {
			targetTemporaryMenuItem.setSelected(targetFilterItemsAction.isSelected());
		}
	}

	/**
	 * Rebuilds the trees only displaying paths that have mapped items.
	 */
	private void filterTreeOnMappingType(MappingJTree tree, KnowledgeBase kb, JToggleButton toggleButton, MappingType mappingType) {
		if (toggleButton.isSelected()) {
			Collection<Collection> results = TreeFilterManager.getInstance().performMapFilter(kb, mappingType);
			if (results != null) {
				tree.initTree(results, true);
			}
		} else {
			tree.initTree();
		}
	}

	/**
	 * Sets the filterToMappedItems variable and re-initializes the trees if necessary.
	 */
	private void filterToMappedItems() {
		filterToMappedItems = !filterToMappedItems;
		if (!filterToMappedItems) { // reset the trees
			TreeFilterManager.getInstance().reset();
			getSourceTree().initTree(null, true);
			TreeFilterManager.getInstance().reset();
			getTargetTree().initTree(null, true);
		} else { // apply the filter based on the source selection
			TreePath path = sourceTree.getSelectionPath();
			updateTreeSelection(path, targetTree, null);
		}

		filterToMappedItemsAction.setSelected(filterToMappedItems);
		filterBasedOnMappingsMenuItem.setSelected(filterToMappedItems);
	}

	private void filterBackgroundItems() {
		arcRenderer.setFilterBackgroundArcs(!arcRenderer.getFilterBackgroundArcs());
		filterBackgroundItemsMenuItem.setSelected(arcRenderer.getFilterBackgroundArcs());
		filterBackgroundItemsAction.setSelected(arcRenderer.getFilterBackgroundArcs());

		repaintArcsPanel();
	}

	/**
	 * Applies the mapped items filter. This filters the opposite tree of the currently selected
	 * path to only show the paths necessary to view the potential/verified mapped items.
	 */
	private void filterToMappedItems(List<AbstractMapElement> mapElements, MappingJTree tree, Frame selectedFrame) {
		if (filterToMappedItems) {
			if (mapElements != null && mapElements.size() > 0) {
				int activeSearchIndex = (tree.isSource()) ? TreeFilterManager.SOURCE_SEARCH : TreeFilterManager.TARGET_SEARCH;
				TreeFilterManager.getInstance().setActiveSearch(activeSearchIndex);

				Collection<Collection> results = TreeFilterManager.getInstance().performFrameToMapFilter(mapElements, selectedFrame);
				if (results != null) {
					tree.initTree(results, true);
				}
			}
		} else {
			tree.initTree();
		}
	}

	/**
	 * Selects all TreePaths that are part of the mapElements list.
	 * 
	 * @return True if some item was selected, otherwise False.
	 */
	private void selectTreePath(ArrayList<AbstractMapElement> mapElements, MappingJTree tree, boolean overwriteSelectionPaths) {
		boolean selectionResult = tree.selectTreePath(mapElements, overwriteSelectionPaths);
		
		if(mapElements != null) {
			displayNotification(mapElements, tree, selectionResult);
		}

		// set the currently selected element
		if (mapElements != null && mapElements.size() > 0) {
			AbstractMapElement mapElement = mapElements.get(0);
			if(!mapElement.getMappingType().equals(MappingType.VERIFIED_MAPPING)) {
				selectSuggestion(mapElement);
			}
			else {
				selectCompletedMapping(mapElement);
				clsPropertyPanel.setSelectedMapping((VerifiedMapElement)mapElement);
			}
			arcRenderer.setMappedItemSelected(true);
			if (tree.isSource()) {
				currentSourcePath = mapElements.get(0).getSourcePath();
			} else {
				currentTargetPath = mapElements.get(0).getTargetPath();
			}
		} else {
			currentMapElement.clear();
			arcRenderer.setMappedItemSelected(false);
		}

		initNavigationButtons();
	}
	
	private void displayNotification(ArrayList<AbstractMapElement> mapElements, MappingJTree tree, boolean mappingExists) {
		MappingJTree tempTree = sourceTree;
		if(tree.isSource()) tempTree = targetTree;
		
		NotificationManager.getInstance().createNotification(tempTree, mappingExists);
	}

	/**
	 * Updates the central panel actions based on whether an existing mapping is selected or not.
	 * 
	 * @param existing True if an existing mapping has been selected in the view.
	 */
	private void updateActions() {
		addMappingAction.setEnabled(false);
		addTemporaryMappingAction.setEnabled(false);
		removeMappingAction.setEnabled(false);
		approveMappingAction.setEnabled(false);
		temporaryMappingAction.setEnabled(false);

		showMappingsAction.setEnabled(true);

		if (isSingleFrameSelected(sourceTree) && isSingleFrameSelected(targetTree)) {
			boolean existing = currentMapElement != null && currentMapElement.size() > 0;
			addMappingAction.setEnabled(!existing);
			addTemporaryMappingAction.setEnabled(!existing);
			removeMappingAction.setEnabled(existing);

			if (existing && !(currentMapElement instanceof VerifiedMapElement)) {
				approveMappingAction.setEnabled(true);
				if (currentMapElement instanceof CandidateMapElement) {
					temporaryMappingAction.setEnabled(true);
				}
			}
		}
	}

	/**
	 * Determines if the selected paths in the given tree all correspond to the same ontological
	 * Frame.
	 * 
	 * @param tree
	 * @return True if all the selected nodes correspond to the same Frame.
	 */
	private boolean isSingleFrameSelected(JTree tree) {
		if (tree.getSelectionCount() > 0) {
			TreePath path = tree.getSelectionPaths()[0];
			Object selectedObject = null;
			if (path.getLastPathComponent() instanceof LazyTreeNode) {
				selectedObject = ((LazyTreeNode) path.getLastPathComponent()).getUserObject();
			}
			for (int i = 1; i < tree.getSelectionPaths().length; i++) {
				path = tree.getSelectionPaths()[i];
				if (path.getLastPathComponent() instanceof LazyTreeNode) {
					LazyTreeNode treeNode = (LazyTreeNode) path.getLastPathComponent();
					if (!treeNode.getUserObject().equals(selectedObject)) {
						return false;
					}
				}

			}
		}
		return tree.getSelectionCount() > 0;
	}

	/**
	 * Adds a new mapping between the two tree paths.
	 */
	@SuppressWarnings("unchecked")
	protected void addMapping(TreePath sourceTreePath, TreePath targetTreePath) {
		// get the source and target classes
		Frame sourceFrame = (Frame) ((LazyTreeNode) sourceTreePath.getLastPathComponent()).getUserObject();
		Frame targetFrame = (Frame) ((LazyTreeNode) targetTreePath.getLastPathComponent()).getUserObject();

		// show Prompt's operation creation dialog
		JDialog dialog = new JDialog(this.parentFrame, "Creation Operation");
		CreateNewOperationPane panel = new CreateNewOperationPane(dialog);
		if(sourceFrame instanceof Slot && targetFrame instanceof Slot) {
			panel.setEditor(MergeSlotsOperationEditor.class);
		}
		
		panel.addArgument(sourceFrame);
		panel.addArgument(targetFrame);
		
		dialog.setModal(true);
		dialog.setLocationRelativeTo(this.parentFrame);
		dialog.getContentPane().add(panel);
		dialog.pack();
		dialog.setVisible(true);

		arcRenderer.setDragPoint(null);

		// redraw the arcs
		repaintArcsPanel();
	}

	/**
	 * Gets the first source and target tree paths and creates a mapping between them.
	 */
	private void addMapping() {
		TreePath[] sourcePaths = sourceTree.getSelectionPaths();
		TreePath[] targetPaths = targetTree.getSelectionPaths();

		if (sourcePaths != null && targetPaths != null && sourcePaths.length > 0 && targetPaths.length > 0) {
			addMapping(sourcePaths[sourcePaths.length - 1], targetPaths[targetPaths.length - 1]);
		}
	}

	/**
	 * Gets the first source and target tree paths and creates a temporary mapping between them.
	 */
	private void addTemporaryMapping() {
		// get the source and target classes
		Cls sourceCls = (Cls) TreeUtil.getLastSelectedUserObject(sourceTree);
		Cls targetCls = (Cls) TreeUtil.getLastSelectedUserObject(targetTree);

		if (sourceCls != null && targetCls != null) {
			Operation operation = MergeFramesOperation.selectMergeOperation(sourceCls, targetCls, null);
			operation.setPriority(0);

			DataLoaderFacade.getInstance().getTemporaryMappingManager().addTemporaryMapping(operation);
			DataLoaderFacade.getInstance().refreshMapping();
		}
	}

	/**
	 * Adds the currently selected map element to the list of temporary mapping elements and
	 * refreshes the mapping information as well as the view.
	 */
	private void markAsTemporary() {
		if (currentMapElement != null) {
			Operation operation = DataLoaderFacade.getInstance().getSuggestionsManager().getOperationForMapping(currentMapElement.get(0));
			DataLoaderFacade.getInstance().getTemporaryMappingManager().addTemporaryMapping(operation);
			DataLoaderFacade.getInstance().refreshMapping();
		}
	}

	/**
	 * Called to convert a temporary/candidate mapping element into a real mapping element.
	 */
	private void markAsMapped() {
		try {
			if (currentMapElement != null) {
				this.setCursor(new Cursor(Cursor.WAIT_CURSOR));
				
				for(AbstractMapElement mapElement : currentMapElement) {
					int result = JOptionPane.YES_OPTION;
					
					String question = "";
					if(mappingCollection.isSourceFrameMapped(mapElement.getSourceFrame(), MappingType.VERIFIED_MAPPING)) {
						question = mapElement.getSourceFrame().getBrowserText();
					}
					if(mappingCollection.isTargetFrameMapped(mapElement.getTargetFrame(), MappingType.VERIFIED_MAPPING)) {
						if(question.length() > 0) question += " and ";
						question += mapElement.getTargetFrame().getBrowserText();
					}
				
					if(question.length() > 0) {
						result = JOptionPane.showConfirmDialog(this.parentFrame, "Frame(s) " + question + " already has a mapping.  Continue?\n\n", "Mark as mapping?", JOptionPane.YES_NO_OPTION,
								JOptionPane.QUESTION_MESSAGE);
					}
					
					if (result == JOptionPane.YES_OPTION) {
						if (mapElement instanceof TemporaryMapElement) {
							Operation operation = DataLoaderFacade.getInstance().getTemporaryMappingManager().getOperationForMapping(mapElement);
							operation.performOperation();
						} else if (mapElement instanceof CandidateMapElement) {
							Operation operation = DataLoaderFacade.getInstance().getSuggestionsManager().getOperationForMapping(mapElement);
							operation.performOperation();
						}
					}
				}
				currentMapElement.clear();
			}
		} finally {
			this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
		}
	}

	/**
	 * Removes the currently selected mapping item.
	 */
	private void removeMapping() {
		if (currentMapElement != null && currentMapElement.size() > 0) {
			for(AbstractMapElement mapElement : currentMapElement) {
				String mapping = mapElement.getSourceFrame().getBrowserText() + " -> " + mapElement.getTargetFrame().getBrowserText();
				int result = JOptionPane.showConfirmDialog(this.parentFrame, "Are you sure you want to remove this mapping?\n\n" + mapping, "Delete mapping?", JOptionPane.YES_NO_OPTION,
						JOptionPane.QUESTION_MESSAGE);
				if (result == JOptionPane.YES_OPTION) {
					mappingCollection.removeMappings(mapElement);
	
					// refresh the Prompt list
					ListSplitPane splitPane = TabComponent.getTodoPane();
					ActionListPane listPane = ((SuggestionListPane) splitPane).getTodoList();
					listPane.repaint();
					
					removeMappingAction.setEnabled(false);
				}
			}
			currentMapElement.clear();
			DataLoaderFacade.getInstance().refreshMapping();
		}
	}

	/**
	 * Resizes the layered JPanels that are displayed.
	 */
	private void resizeLayers() {
		Dimension size = layeredPane.getSize();
		getToolbarPanel().setSize(new Dimension(size.width, MAIN_PANEL_LOCATION));
		getMainPanel().setSize(new Dimension(size.width, size.height - MAIN_PANEL_LOCATION));

		getArcsPanel().setSize(new Dimension(size.width, size.height - ArcRenderer.TOP_PANEL_LOCATION - getBottomPane().getHeight()));
		getToolTipsPanel().setSize(size);
		layeredPane.validate();
	}

	/**
	 * Expands tree paths for both trees, displaying all mapped items.
	 */
	private void showAllMappings() {
		TreeFilterManager.getInstance().setActiveSearch(TreeFilterManager.SOURCE_SEARCH);
		ArrayList<TreePath> sourceMappingPaths = TreeUtil.getTreePaths(sourceTree, mappingCollection.getSourceFrames().keySet(), true);
		TreeUtil.expandPaths(getSourceTree(), sourceMappingPaths);

		TreeFilterManager.getInstance().setActiveSearch(TreeFilterManager.TARGET_SEARCH);
		ArrayList<TreePath> targetMappingPaths = TreeUtil.getTreePaths(targetTree, mappingCollection.getTargetFrames().keySet(), true);
		TreeUtil.expandPaths(getTargetTree(), targetMappingPaths);
	}

	private void showOnlyExactNameMatches() {
		if (mappingCollection.getOperationFilter() == CandidationOperationFilter.NO_FILTER) {
			mappingCollection.setOperationFilter(CandidationOperationFilter.EXACT_NAME_FILTER);
			showOnlyExactMatchesAction.setSelected(true);
			exactNameFilterMenuItem.setSelected(true);

		} else {
			mappingCollection.setOperationFilter(CandidationOperationFilter.NO_FILTER);
			showOnlyExactMatchesAction.setSelected(false);
			exactNameFilterMenuItem.setSelected(false);
		}

		refreshTrees();
	}

	/**
	 * Attempts to select the next mapping that is "up" the tree.
	 */
	private void moveUp(MappingJTree tree) {
		Collection<Frame> objectPath;
		if (tree.isSource()) {
			objectPath = mappingCollection.getPreviousPath(currentSourcePath, mappingCollection.getSourcePaths());
			currentSourcePath = objectPath;
		} else {
			objectPath = mappingCollection.getPreviousPath(currentTargetPath, mappingCollection.getTargetPaths());
			currentTargetPath = objectPath;
		}
		initNavigationButtons();
		TreeUtil.expandAndSelectPath(tree, TreeUtil.getTreePath(tree, objectPath));
	}

	/**
	 * Attempts to select the next mapping that is "down" the tree.
	 */
	private void moveDown(MappingJTree tree) {
		Collection<Frame> objectPath;
		if (tree.isSource()) {
			objectPath = mappingCollection.getNextPath(currentSourcePath, mappingCollection.getSourcePaths());
			currentSourcePath = objectPath;
		} else {
			objectPath = mappingCollection.getNextPath(currentTargetPath, mappingCollection.getTargetPaths());
			currentTargetPath = objectPath;
		}
		initNavigationButtons();
		TreeUtil.expandAndSelectPath(tree, TreeUtil.getTreePath(tree, objectPath));
	}

	/**
	 * Sets the zoomSync flag. If it's true, the target zoom slider gets synced immediately with the
	 * source zoom slider.
	 */
	private void setZoomSync(boolean zoomSync) {
		if (zoomSync != this.zoomSync) {
			this.zoomSync = zoomSync;

			if (zoomSync) {
				targetSlider.setValue(sourceSlider.getValue());
				repaintArcsPanel();
			}
		}
	}

	/**
	 * Updates the semantic zoom flag for each ZoomManager.
	 */
	private void updateSemanticZoom(boolean semanticZoom) {
		sourceTree.getZoomManager().setSemanticZoom(semanticZoom);
		targetTree.getZoomManager().setSemanticZoom(semanticZoom);

		repaintArcsPanel();
		refreshTrees();

		SwingUtilities.invokeLater(new Runnable() {

			public void run() {
				if (currentSourcePath != null) {
					sourceTree.scrollPathToVisible(TreeUtil.getTreePath(sourceTree, currentSourcePath));
				}
				if (currentTargetPath != null) {
					targetTree.scrollPathToVisible(TreeUtil.getTreePath(targetTree, currentTargetPath));
				}
			}
		});
	}

	/**
	 * Update the overall zoom for a given ZoomManager and ZoomableTreeUI
	 */
	private void updateTreeZoom(int zoom, final MappingJTree tree) {
		tree.getZoomManager().setOverallZoom(zoom);
		repaintArcsPanel();

		tree.refreshTree();
	}

	/**
	 * Selects the srcFrame in the source tree and the targetFrame in the target ontology tree.
	 * 
	 * @param srcFrame
	 * @param targetFrame
	 */
	private void selectFramesInTrees(Frame srcFrame, Frame targetFrame) {
		AbstractMapElement mapElement = mappingCollection.getMapElement(srcFrame, targetFrame);
		if (mapElement != null) {
			TreePath path = TreeUtil.getTreePath(sourceTree, mapElement.getSourcePath());
			if (path == null && filterToMappedItems) {
				ArrayList<AbstractMapElement> mapElements = new ArrayList<AbstractMapElement>();
				mapElements.add(mapElement);

				filterToMappedItems(mapElements, sourceTree, srcFrame);
			} else {
				ignoreSelection = true;				
				TreeUtil.expandAndSelectPath(sourceTree, path);
				updateTreeSelection(path, targetTree, targetFrame);
				ignoreSelection = false;
			}

			refreshTrees();
			updateActions();
		}
	}
	
	/**
	 * NOTE: this is only for suggestions and temporary mappings.
	 * 
	 * Given the mapElement, the suggestions are checked for the corresponding operation and the 
	 * matching suggestion is selected in the list.  As a consequence, the matching source and target
	 * frames will be selected in the source and target trees.
	 */
	private void selectSuggestion(AbstractMapElement mapElement) {
		int row = 0;
		ignoreSuggestionSelection = true;
		for(Object o : suggestionListPane.getActionList()) {
			Operation operation = (Operation)o;
			if(operation.equals(mapElement.getOperation())) {
				break;
			}
			row++;
		}
		
		if(row < suggestionListPane.getActionList().size()) {
			suggestionListPane.setSelectedRow(row);
		}
	}
	
	/**
	 * NOTE: this is only for verified mappings.
	 * 
	 * Given the mapElement, the completed mappings are checked for the corresponding map element and the 
	 * matching mapping is selected in the list.  
	 */
	private void selectCompletedMapping(AbstractMapElement mapElement) {
		int row = 0;
		ignoreSuggestionSelection = true;
		for(; row < completedMappingsPanel.getMappingsTable().getRowCount(); row++) {
			AbstractMapElement tableMapElement = (AbstractMapElement)completedMappingsPanel.getMappingsTable().getModel().getValueAt(row, 0);
			if(tableMapElement.equals(mapElement)) {
				completedMappingsPanel.getMappingsTable().getSelectionModel().setSelectionInterval(row, row);
				break;
			}
		}
	}

	// ///////////////////////////////
	// GUI INITIALIZATION

	/**
	 * Sets up the enabled value for the mapping item navigation.
	 */
	private void initNavigationButtons() {
		sourceMappingMoveUpAction.setEnabled(true);
		sourceMappingMoveDownAction.setEnabled(true);

		if (mappingCollection.isLastPath(currentSourcePath, mappingCollection.getSourcePaths())) {
			sourceMappingMoveDownAction.setEnabled(false);
		}
		if (currentSourcePath == null || mappingCollection.isFirstPath(currentSourcePath, mappingCollection.getSourcePaths())) {
			sourceMappingMoveUpAction.setEnabled(false);
		}

		targetMappingMoveUpAction.setEnabled(true);
		targetMappingMoveDownAction.setEnabled(true);

		if (mappingCollection.isLastPath(currentTargetPath, mappingCollection.getTargetPaths())) {
			targetMappingMoveDownAction.setEnabled(false);
		}
		if (mappingCollection.isFirstPath(currentTargetPath, mappingCollection.getTargetPaths())) {
			targetMappingMoveUpAction.setEnabled(false);
		}
	}
	
	private AbstractAction getShowSlotsAction() {
		return new AbstractAction() {
			private static final long serialVersionUID = 4068512381328802498L;

			public void actionPerformed(ActionEvent e) {
				DataLoaderFacade.getInstance().setShowSlots(showSlotsMenuItem.isSelected());
				sourceTree.resetTree();
				targetTree.resetTree();
			}
		};
	}

	private AbstractAction getExactNameFilterAction() {
		return new AbstractAction("", IconConstants.ICON_SHOW_EXACT_MAPPINGS) {
			private static final long serialVersionUID = 4068512381328802497L;

			public void actionPerformed(ActionEvent e) {
				showOnlyExactNameMatches();
			}
		};
	}

	private AbstractAction getFilterBackgroundItemsAction() {
		return new AbstractAction("", IconConstants.ICON_FUNNEL_FILTER) {
			private static final long serialVersionUID = -6907171672812999837L;

			public void actionPerformed(ActionEvent e) {
				filterBackgroundItems();
			}
		};
	}

	private AbstractAction getFilterToMappedItemsAction() {
		return new AbstractAction("", IconConstants.ICON_FILTER) {
			private static final long serialVersionUID = 3444234093837201622L;

			public void actionPerformed(ActionEvent e) {
				filterToMappedItems();
			}
		};
	}

	private AbstractAction getCarryOutExactNameMatchesActions() {
		return new AbstractAction("") {
			private static final long serialVersionUID = -3849115482787048897L;

			public void actionPerformed(ActionEvent e) {
				int answer = JOptionPane.showConfirmDialog(MappingVisualizationPanel.this, "Performing all exact name matches may take a while, are you sure you want to proceed?", "Please confirm",
						JOptionPane.YES_NO_OPTION);
				if (answer == JOptionPane.YES_OPTION) {
					try {
						parentFrame.setCursor(new Cursor(Cursor.WAIT_CURSOR));
						mappingCollection.performExactNameMatches();
					} catch (Exception ex) {
						JOptionPane.showMessageDialog(MappingVisualizationPanel.this, ex.getMessage());
					} finally {
						parentFrame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
					}
				}
			}
		};
	}

	/**
	 * Creates the toolbar actions.
	 */
	@SuppressWarnings({ "serial" })
	private void createActions() {
		sourceMappingMoveUpAction = new AbstractAction(PREVIOUS_MAPPING_LABEL, IconConstants.MAPPINGS_MOVE_UP) {
			public void actionPerformed(ActionEvent e) {
				moveUp(getSourceTree());
			}
		};
		sourceMappingMoveUpAction.putValue(Action.SHORT_DESCRIPTION, PREVIOUS_MAPPING_LABEL);
		sourceMappingMoveDownAction = new AbstractAction(NEXT_MAPPING_LABEL, IconConstants.MAPPINGS_MOVE_DOWN) {
			public void actionPerformed(ActionEvent e) {
				moveDown(getSourceTree());
			}
		};
		sourceMappingMoveDownAction.putValue(Action.SHORT_DESCRIPTION, NEXT_MAPPING_LABEL);
		targetMappingMoveUpAction = new AbstractAction(PREVIOUS_MAPPING_LABEL, IconConstants.MAPPINGS_MOVE_UP) {
			public void actionPerformed(ActionEvent e) {
				moveUp(getTargetTree());
			}
		};
		targetMappingMoveUpAction.putValue(Action.SHORT_DESCRIPTION, PREVIOUS_MAPPING_LABEL);
		targetMappingMoveDownAction = new AbstractAction(NEXT_MAPPING_LABEL, IconConstants.MAPPINGS_MOVE_DOWN) {
			public void actionPerformed(ActionEvent e) {
				moveDown(getTargetTree());
			}
		};
		targetMappingMoveDownAction.putValue(Action.SHORT_DESCRIPTION, PREVIOUS_MAPPING_LABEL);
		showMappingsAction = new AbstractAction("", IconConstants.EXPAND_MAPPINGS) {
			public void actionPerformed(ActionEvent e) {
				showAllMappings();
			}
		};
		showMappingsAction.putValue(Action.SHORT_DESCRIPTION, SHOW_ONLY_EXACT_NAME_MATCHES);

		sourceFilterItemsAction = new JToggleButton(new AbstractAction("", IconConstants.FILTER_CANDIDATE) {
			public void actionPerformed(ActionEvent e) {
				filterSourceMappings(currentSourceFilterType == null ? MappingType.CANDIDATE_MAPPING : currentSourceFilterType);
			}
		});
		sourceFilterItemsAction.setToolTipText(SHOW_ONLY_CANDIDATE_ITEMS);
		targetFilterItemsAction = new JToggleButton(new AbstractAction("", IconConstants.FILTER_CANDIDATE) {
			public void actionPerformed(ActionEvent e) {
				filterTargetMappings(currentTargetFilterType == null ? MappingType.CANDIDATE_MAPPING : currentTargetFilterType);
			}
		});
		targetFilterItemsAction.setToolTipText(SHOW_ONLY_CANDIDATE_ITEMS);

		// global filter actions
		showOnlyExactMatchesAction = new JToggleButton(getExactNameFilterAction());
		showOnlyExactMatchesAction.setToolTipText(SHOW_ONLY_EXACT_NAME_MATCHES);

		filterToMappedItemsAction = new JToggleButton(getFilterToMappedItemsAction());
		filterToMappedItemsAction.setToolTipText(SHOW_ONLY_CURRENT_SELECTIONS_MAPPED_ITEMS);

		filterBackgroundItemsAction = new JToggleButton(getFilterBackgroundItemsAction());
		filterBackgroundItemsAction.setToolTipText(FILTER_BACKGROUND_ITEMS_ON_SELECTION);

		addMappingAction = new AbstractAction(ADD_MAPPING_LABEL, IconConstants.ICON_ADD_MAPPING) {
			public void actionPerformed(ActionEvent e) {
				addMapping();
			}
		};
		addMappingAction.putValue(Action.SHORT_DESCRIPTION, ADD_MAPPING_LABEL);
		addMappingAction.setEnabled(false);
		removeMappingAction = new AbstractAction(REMOVE_MAPPING_LABEL, IconConstants.ICON_REMOVE_MAPPING) {
			public void actionPerformed(ActionEvent e) {
				removeMapping();
			}
		};
		removeMappingAction.putValue(Action.SHORT_DESCRIPTION, REMOVE_MAPPING_LABEL);
		removeMappingAction.setEnabled(false);
		approveMappingAction = new AbstractAction(MARK_AS_MAPPED_LABEL, IconConstants.ICON_MARK_AS_MAPPED) {
			public void actionPerformed(ActionEvent e) {
				markAsMapped();
			}
		};
		approveMappingAction.putValue(Action.SHORT_DESCRIPTION, MARK_AS_MAPPED_LABEL);
		approveMappingAction.setEnabled(false);

		temporaryMappingAction = new AbstractAction(MARK_AS_TEMPORARY_LABEL, IconConstants.ICON_MARK_AS_TEMPORARY) {
			public void actionPerformed(ActionEvent e) {
				markAsTemporary();
			}
		};
		temporaryMappingAction.putValue(Action.SHORT_DESCRIPTION, MARK_AS_TEMPORARY_LABEL);
		temporaryMappingAction.setEnabled(false);
		addTemporaryMappingAction = new AbstractAction(ADD_TEMPORARY_MAPPING_LABEL, IconConstants.ICON_ADD_TEMPORARY_MAPPING) {
			public void actionPerformed(ActionEvent e) {
				addTemporaryMapping();
			}
		};
		addTemporaryMappingAction.putValue(Action.SHORT_DESCRIPTION, ADD_TEMPORARY_MAPPING_LABEL);
		addTemporaryMappingAction.setEnabled(false);

		sourceFilterButton = new DropDownButton(sourceFilterItemsAction);
		sourceFilterButton.setMenu(getSourceMenuItems());
		sourceFilterButton.setEnabled(true);

		targetFilterButton = new DropDownButton(targetFilterItemsAction);
		targetFilterButton.setMenu(getTargetMenuItems());
		targetFilterButton.setEnabled(true);
	}

	private void initListeners() {
		// refreshes the trees if the visibility of a mapping type changes
		mappingCollection.getMappingTypeManager().addMappingTypeChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				refreshTrees();
			}
		});

		// executed when CogZ is closed
		this.addContainerListener(new ContainerAdapter() {
			public void componentRemoved(ContainerEvent e) {
				saveProperties();
				BrowserLaunch.removeReports();
			}

		});

		mappingMouseListener = new MappingPanelMouseListener(this);
	}

	/**
	 * Initializes this panel with the information about the state from the last time this panel was
	 * opened.
	 */
	private void initProperties() {
		int splitPosition = PropertiesManager.getInstance().getPropertyAsInt(MappingVisualizationProperties.VERTICAL_SPLIT_POSITION);

		if (splitPosition > 0) {
			verticalSplitPane.setDividerLocation(splitPosition);
		}

		splitPosition = PropertiesManager.getInstance().getPropertyAsInt(MappingVisualizationProperties.SOURCE_SPLIT_POSITION);
		if (splitPosition > 0) {
			mainSplitPane.setDividerLocation(splitPosition);
		}

		splitPosition = PropertiesManager.getInstance().getPropertyAsInt(MappingVisualizationProperties.TARGET_SPLIT_POSITION);
		if (splitPosition > 0) {
			innerSplitPane.setDividerLocation(splitPosition);
		}

		numOfRuns = Math.max(0, PropertiesManager.getInstance().getPropertyAsInt(MappingVisualizationProperties.NUM_OF_RUNS));
	}

	/**
	 * Saves this panel's current state to the properties file.
	 */
	private void saveProperties() {
		PropertiesManager.getInstance().setProperty(MappingVisualizationProperties.VERTICAL_SPLIT_POSITION, verticalSplitPane.getDividerLocation() + "");
		PropertiesManager.getInstance().setProperty(MappingVisualizationProperties.SOURCE_SPLIT_POSITION, mainSplitPane.getDividerLocation() + "");
		PropertiesManager.getInstance().setProperty(MappingVisualizationProperties.TARGET_SPLIT_POSITION, innerSplitPane.getDividerLocation() + "");
		PropertiesManager.getInstance().setProperty(MappingVisualizationProperties.NUM_OF_RUNS, (numOfRuns + 1) + "");
	}

	private void initialize() {
		add(getLayeredPane(), BorderLayout.CENTER);

		getMenuBar();

		// lazy initialization of source and target for the ArcRenderer
		arcRenderer.setSourceTree(sourceTree);
		arcRenderer.setTargetTree(targetTree);

		// initialize mapping navigation buttons
		initNavigationButtons();

		mappingMouseListener.initUIReferences();
		initProperties();
	}

	private MappingTypeFilterDialog getMappingTypeFilterDialog() {
		if (mappingTypeFilterDialog == null) {
			mappingTypeFilterDialog = new MappingTypeFilterDialog(parentFrame, mappingCollection.getMappingTypeManager());
		}

		return mappingTypeFilterDialog;
	}

	private JLayeredPane getLayeredPane() {
		if (layeredPane == null) {
			layeredPane = new JLayeredPane();
			layeredPane.addComponentListener(new ComponentAdapter() {
				public void componentResized(ComponentEvent e) {
					resizeLayers();
				}
			});
			layeredPane.add(getToolbarPanel(), new Integer(2));
			layeredPane.add(getMainPanel(), new Integer(0));
			layeredPane.add(getArcsPanel(), new Integer(1));
			layeredPane.add(getToolTipsPanel(), new Integer(3));
			resizeLayers();
		}
		return layeredPane;
	}

	private JPanel getToolbarPanel() {
		if (toolbarPanel == null) {
			toolbarPanel = new JPanel(new BorderLayout());
			Cls sourceRootCls = DataLoaderFacade.getInstance().getSourceOntology().getRootCls();
			Cls targetRootCls = DataLoaderFacade.getInstance().getTargetOntology().getRootCls();

			sourceMappingProgressPanel = new MappingProgressPanel(this, sourceStatsManager, sourceRootCls, true);
			targetMappingProgressPanel = new MappingProgressPanel(this, targetStatsManager, targetRootCls, false);

			toolbarPanel.add(createTitlePanel(getSourceTitle(), getSourceTree(), sourceMappingProgressPanel), BorderLayout.WEST);
			toolbarPanel.add(createTitlePanel(getTargetTitle(), getTargetTree(), targetMappingProgressPanel), BorderLayout.EAST);
			toolbarPanel.add(getMiddlePanel(), BorderLayout.CENTER);
		}
		return toolbarPanel;
	}

	/**
	 * Creates the panel that displays the two ontology trees and the mapping arcs between them.
	 */
	private JPanel getMainPanel() {
		if (mainPanel == null) {
			mainPanel = new JPanel(new BorderLayout());
			mainPanel.setLocation(0, 32);

			verticalSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
			mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
			innerSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);

			verticalSplitPane.setBorder(null);
			mainSplitPane.setBorder(null);
			innerSplitPane.setBorder(null);

			JPanel panel = new JPanel(new BorderLayout());
			panel.add(getSourceSearchText(), BorderLayout.NORTH);
			panel.add(getScrollPane(getSourceTree(), ComponentOrientation.RIGHT_TO_LEFT), BorderLayout.CENTER);
			panel.setPreferredSize(new Dimension(250, 600));
			panel.add(getSourceSlider(), BorderLayout.SOUTH);

			mainSplitPane.add(panel);

			panel = new JPanel(new BorderLayout());
			panel.add(getTargetSearchText(), BorderLayout.NORTH);
			panel.add(getScrollPane(getTargetTree(), ComponentOrientation.LEFT_TO_RIGHT), BorderLayout.CENTER);
			panel.add(getTargetSlider(), BorderLayout.SOUTH);
			panel.setPreferredSize(new Dimension(250, 600));

			innerSplitPane.add(getArcsMiddleHoverPanel());
			innerSplitPane.add(panel);
			innerSplitPane.setResizeWeight(1.0);
			innerSplitPane.setOneTouchExpandable(true);

			mainSplitPane.add(innerSplitPane);
			mainSplitPane.setOneTouchExpandable(true);

			verticalSplitPane.add(mainSplitPane);
			verticalSplitPane.add(getBottomPane());
			verticalSplitPane.setOneTouchExpandable(true);

			int height = (int) (PromptUIPlugin.getSize().height * 0.6);
			verticalSplitPane.setDividerLocation(height);

			mainPanel.add(verticalSplitPane, BorderLayout.CENTER);
		}
		return mainPanel;
	}

	private JTabbedPane getBottomPane() {
		if (bottomPanel == null) {
			bottomPanel = new JTabbedPane();
			bottomPanel.add("Suggestion list", getSuggestionListPanel());
			bottomPanel.add("Completed mappings", getCompletedMappingsPanel());
			bottomPanel.add("Neighborhood view", getNeighborhoodPanel());
			
			// FIXME
			getPropertyPanel();
			getAtlRulePanel();
			//bottomPanel.add("Properties", getPropertyPanel());
			//bottomPanel.add("Mapping rules", getAtlRulePanel());

			bottomPanel.addMouseListener(new MouseAdapter() {
				public void mouseClicked(MouseEvent e) {
					if (e.getClickCount() == 2) {
						createAndShowExtractedDialog();
					} else if (SwingUtilities.isRightMouseButton(e)) {
						getTabPopupMenu().show(bottomPanel, e.getX(), e.getY());
					}
				}
			});

			// force the neighborhood panel to update when the tab becomes visible
			bottomPanel.addChangeListener(new ChangeListener() {
				public void stateChanged(ChangeEvent e) {
					if (bottomPanel.getSelectedIndex() == 1) {
						getNeighborhoodPanel().showNeighborhoods();
					}
				}

			});

		}
		return bottomPanel;
	}

	private JPopupMenu getTabPopupMenu() {
		if (tabPopupMenu == null) {
			tabPopupMenu = new JPopupMenu();
			tabPopupMenu.add(new AbstractAction("Detach") {
				private static final long serialVersionUID = 1970887140681566431L;

				public void actionPerformed(ActionEvent e) {
					createAndShowExtractedDialog();
				}
			});
			tabPopupMenu.addSeparator();
			tabPopupMenu.add(new AbstractAction("Minimize") {
				private static final long serialVersionUID = -6941348321220486967L;

				public void actionPerformed(ActionEvent e) {
					if (bottomPanel.getHeight() > 50) {
						previousDividerLocation = getMainCentralPanel().getHeight();
						verticalSplitPane.setDividerLocation(0.98);
					}
				}
			});
			tabPopupMenu.add(new AbstractAction("Restore") {
				private static final long serialVersionUID = -1803577249546963613L;

				public void actionPerformed(ActionEvent e) {
					verticalSplitPane.setDividerLocation(previousDividerLocation);
				}
			});
		}

		return tabPopupMenu;
	}

	/**
	 * Creates a dialog as an external window to display a component object that is currently in a
	 * tabbed pane.
	 */
	private void createAndShowExtractedDialog() {
		final Component component = bottomPanel.getSelectedComponent();
		final String title = bottomPanel.getTitleAt(bottomPanel.getSelectedIndex());
		final int tabIndex = bottomPanel.getSelectedIndex();

		JFrame topLevelContainer = (JFrame) SwingUtilities.windowForComponent(component);

		JDialog dialog = new JDialog(topLevelContainer);
		dialog.add(component);
		dialog.setTitle(title);
		dialog.setSize(new Dimension(800, 600));

		dialog.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				if (bottomPanel.getTabCount() == 0) {
					verticalSplitPane.setDividerLocation(previousDividerLocation);
				}
				bottomPanel.insertTab(title, null, component, title, tabIndex);
				bottomPanel.setSelectedIndex(tabIndex);
			}
		});

		dialog.setVisible(true);

		if (bottomPanel.getTabCount() == 0) {
			bottomPanel.setSize(0, 0);
			previousDividerLocation = verticalSplitPane.getDividerLocation();
			verticalSplitPane.setDividerLocation(1.0);
		}
	}
	
	private ClsPropertyPanel getPropertyPanel() {
		if(clsPropertyPanel == null) {
			clsPropertyPanel = new ClsPropertyPanel();
		}
		
		return clsPropertyPanel;
	}
	
	private ATLRulePanel getAtlRulePanel() {
		if(atlRulePanel == null) {
			atlRulePanel = new ATLRulePanel();
		}
		return atlRulePanel;
	}

	private NeighborhoodControllerPanel getNeighborhoodPanel() {
		if (neighborhoodPanel == null) {
			neighborhoodPanel = new NeighborhoodControllerPanel(getSourceTree(), getTargetTree());
		}

		return neighborhoodPanel;
	}

	private JScrollPane getScrollPane(MappingJTree tree, ComponentOrientation orientation) {
		// @tag note : needed to make mapping lines update properly
		JScrollPane scrollPane = new JScrollPane(tree) {
			public void repaint(long tm, int x, int y, int width, int height) {
			// RepaintManager.currentManager(this).markCompletelyDirty(getArcsPanel());
			}
		};

		scrollPane.setComponentOrientation(orientation);
		tree.setScrollPane(scrollPane);

		return scrollPane;
	}

	private JMenuBar getMenuBar() {
		if (menuBar == null) {
			menuBar = ProjectManager.getProjectManager().getCurrentProjectMenuBar();
			if(menuBar == null) {
				menuBar = new JMenuBar();
				parentFrame.setJMenuBar(menuBar);
			}

			JMenu fileMenu = new JMenu("File");
			
			JMenuItem newMappingsItem = new JMenuItem("New mapping");
			newMappingsItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					parentFrame.dispose();
					DataLoaderFacade.getInstance().getSourceOntology().clearAllListeners();
					DataLoaderFacade.getInstance().getTargetOntology().clearAllListeners();
					DataLoaderFacade.getInstance().getMappingOntology().clearAllListeners();
					DataLoaderFacade.getInstance().getStandaloneInputFrame().setVisible(true);
				}
			});
			
			JMenuItem saveMappingsItem = new JMenuItem("Save mappings");
			saveMappingsItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					List<Object> errors = new ArrayList<Object>();
					DataLoaderFacade.getInstance().getMappingOntology().getProject().save(errors);
				}
			});
			
			JMenuItem exitMenuItem = new JMenuItem("Exit");
			exitMenuItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					parentFrame.dispose();
				}
			});

			fileMenu.add(newMappingsItem);
			fileMenu.addSeparator();
			fileMenu.add(saveMappingsItem);
			fileMenu.addSeparator();
			fileMenu.add(exitMenuItem);

			if(DataLoaderFacade.getInstance().isStandaloneMode()) {
				cogzMenu = new JMenu("Tools");
			}
			else {
				cogzMenu = new JMenu("CogZ");
			}

			syncZoomMenuItem = new JCheckBoxMenuItem(SYNCHRONIZE_ZOOM_LABEL);
			syncZoomMenuItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					setZoomSync(syncZoomMenuItem.getState());
				}
			});

			semanticZoomMenuItem = new JCheckBoxMenuItem(FISH_EYE_ZOOM_LABEL);
			semanticZoomMenuItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					updateSemanticZoom(semanticZoomMenuItem.getState());
				}
			});

			filterControlMenuItem = new JMenuItem(MAPPING_TYPE_FILTER_LABEL);
			filterControlMenuItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					getMappingTypeFilterDialog().setVisible(true);
				}
			});

			JMenu reportsMenu = new JMenu("Reports");
			for (AbstractReport report : AbstractReport.getReports()) {
				AbstractAction action = new AbstractAction(report.getReportTitle()) {
					private static final long serialVersionUID = 8612062487626563105L;

					public void actionPerformed(ActionEvent e) {
						AbstractReport abstractReport = (AbstractReport) this.getValue("REPORT");
						BrowserLaunch.showReport(abstractReport);
					}
				};
				action.putValue("REPORT", report);

				JMenuItem mappingReport = new JMenuItem(action);

				reportsMenu.add(mappingReport);
			}
			
			showSlotsMenuItem = new JCheckBoxMenuItem(SHOW_SLOTS);
			showSlotsMenuItem.addActionListener(getShowSlotsAction());

			JMenu filterMenu = new JMenu("Global filters");
			exactNameFilterMenuItem = new JCheckBoxMenuItem(SHOW_ONLY_EXACT_NAME_MATCHES);
			filterBasedOnMappingsMenuItem = new JCheckBoxMenuItem(SHOW_ONLY_CURRENT_SELECTIONS_MAPPED_ITEMS);
			filterBackgroundItemsMenuItem = new JCheckBoxMenuItem(FILTER_BACKGROUND_ITEMS_ON_SELECTION);

			exactNameFilterMenuItem.addActionListener(getExactNameFilterAction());
			filterBasedOnMappingsMenuItem.addActionListener(getFilterToMappedItemsAction());
			filterBackgroundItemsMenuItem.addActionListener(getFilterBackgroundItemsAction());

			filterMenu.add(exactNameFilterMenuItem);
			filterMenu.add(filterBasedOnMappingsMenuItem);
			filterMenu.add(filterBackgroundItemsMenuItem);

			JMenu mappingFunctionsMenu = new JMenu("Mapping functions");
			JMenuItem exactNameMenuItem = new JMenuItem("Perform all exact name matches");
			exactNameMenuItem.addActionListener(getCarryOutExactNameMatchesActions());

			mappingFunctionsMenu.add(exactNameMenuItem);

			cogzMenu.add(syncZoomMenuItem);
			cogzMenu.add(semanticZoomMenuItem);
			cogzMenu.addSeparator();
			cogzMenu.add(filterControlMenuItem);
			cogzMenu.add(filterMenu);
			cogzMenu.addSeparator();
			cogzMenu.add(reportsMenu);
			cogzMenu.addSeparator();
			cogzMenu.add(mappingFunctionsMenu);
			cogzMenu.addSeparator();
			cogzMenu.add(showSlotsMenuItem);

			if(DataLoaderFacade.getInstance().isStandaloneMode()) {
				menuBar.add(fileMenu);
			}
			menuBar.add(cogzMenu);

			menuBar.invalidate();
			menuBar.validate();
			menuBar.repaint();
		}
		return menuBar;
	}

	/**
	 * Menu items used in the tree filter drop down button for the source tree.
	 */
	private JMenu getSourceMenuItems() {
		if (sourceFilterMenu == null) {
			sourceFilterMenu = new JMenu();

			sourceMappedMenuItem = new JCheckBoxMenuItem(SHOW_MAPPED_LABEL, IconConstants.FILTER_MAPPINGS);
			sourceMappedMenuItem
					.addActionListener(getSourceFilterMenuActionListener(sourceFilterItemsAction, MappingType.VERIFIED_MAPPING, SHOW_ONLY_MAPPED_ITEMS_LABEL, IconConstants.FILTER_MAPPINGS));
			sourceCandidateMenuItem = new JCheckBoxMenuItem(SHOW_CANDIDATES_LABEL, IconConstants.FILTER_CANDIDATE);
			sourceCandidateMenuItem.addActionListener(getSourceFilterMenuActionListener(sourceFilterItemsAction, MappingType.CANDIDATE_MAPPING, SHOW_ONLY_CANDIDATE_ITEMS,
					IconConstants.FILTER_CANDIDATE));
			sourceTemporaryMenuItem = new JCheckBoxMenuItem(SHOW_TEMPORARY_LABEL, IconConstants.FILTER_CANDIDATE);
			sourceTemporaryMenuItem.addActionListener(getSourceFilterMenuActionListener(sourceFilterItemsAction, MappingType.TEMPORARY_MAPPING, SHOW_ONLY_TEMPORARY_ITEMS,
					IconConstants.FILTER_TEMPORARY));

			sourceFilterMenu.add(sourceCandidateMenuItem);
			sourceFilterMenu.add(sourceMappedMenuItem);
			sourceFilterMenu.add(sourceTemporaryMenuItem);

			sourceFilterMenu.setOpaque(false);
			sourceFilterMenu.setBackground(sourceMappedMenuItem.getBackground());
		}

		return sourceFilterMenu;
	}

	/**
	 * Menu items used in the tree filter drop down button for the target tree.
	 */
	private JMenu getTargetMenuItems() {
		if (targetFilterMenu == null) {
			targetFilterMenu = new JMenu();

			targetMappedMenuItem = new JCheckBoxMenuItem(SHOW_MAPPED_LABEL, IconConstants.FILTER_MAPPINGS);
			targetMappedMenuItem
					.addActionListener(getTargetFilterMenuActionListener(targetFilterItemsAction, MappingType.VERIFIED_MAPPING, SHOW_ONLY_MAPPED_ITEMS_LABEL, IconConstants.FILTER_MAPPINGS));
			targetCandidateMenuItem = new JCheckBoxMenuItem(SHOW_CANDIDATES_LABEL, IconConstants.FILTER_CANDIDATE);
			targetCandidateMenuItem.addActionListener(getTargetFilterMenuActionListener(targetFilterItemsAction, MappingType.CANDIDATE_MAPPING, SHOW_ONLY_CANDIDATE_ITEMS,
					IconConstants.FILTER_CANDIDATE));
			targetTemporaryMenuItem = new JCheckBoxMenuItem(SHOW_TEMPORARY_LABEL, IconConstants.FILTER_TEMPORARY);
			targetTemporaryMenuItem.addActionListener(getTargetFilterMenuActionListener(targetFilterItemsAction, MappingType.TEMPORARY_MAPPING, SHOW_ONLY_TEMPORARY_ITEMS,
					IconConstants.FILTER_TEMPORARY));

			targetFilterMenu.add(targetCandidateMenuItem);
			targetFilterMenu.add(targetMappedMenuItem);
			targetFilterMenu.add(targetTemporaryMenuItem);
		}

		return targetFilterMenu;
	}

	private ActionListener getSourceFilterMenuActionListener(final AbstractButton button, final MappingType mappingType, final String tooltip, final ImageIcon icon) {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (currentSourceFilterType == null || currentSourceFilterType == mappingType) {
					button.setSelected(!button.isSelected());
				} else if (!button.isSelected()) {
					button.setSelected(true);
				}

				filterSourceMappings(mappingType);
				button.setToolTipText(tooltip);
				((AbstractAction) button.getAction()).putValue(Action.SMALL_ICON, icon);
			}
		};
	}

	private ActionListener getTargetFilterMenuActionListener(final AbstractButton button, final MappingType mappingType, final String tooltip, final ImageIcon icon) {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (currentTargetFilterType == null || currentTargetFilterType == mappingType) {
					button.setSelected(!button.isSelected());
				} else if (!button.isSelected()) {
					button.setSelected(true);
				}

				filterTargetMappings(mappingType);
				button.setToolTipText(tooltip);
				((AbstractAction) button.getAction()).putValue(Action.SMALL_ICON, icon);
			}
		};
	}

	private JSlider getSourceSlider() {
		if (sourceSlider == null) {
			sourceSlider = new JSlider(1, 10, ZoomManager.DEFAULT_ZOOM_FACTOR);
			sourceSlider.addChangeListener(new ChangeListener() {
				public void stateChanged(ChangeEvent e) {
					updateTreeZoom(sourceSlider.getValue(), sourceTree);
					if (zoomSync) {
						targetSlider.setValue(sourceSlider.getValue());
					}
				}
			});
		}
		return sourceSlider;
	}

	private JSlider getTargetSlider() {
		if (targetSlider == null) {
			targetSlider = new JSlider(1, 10, ZoomManager.DEFAULT_ZOOM_FACTOR);
			targetSlider.addChangeListener(new ChangeListener() {
				public void stateChanged(ChangeEvent e) {
					updateTreeZoom(targetSlider.getValue(), targetTree);
					if (zoomSync) {
						sourceSlider.setValue(targetSlider.getValue());
					}
				}
			});
		}
		return targetSlider;
	}

	private JTabbedPane getMainCentralPanel() {
		if (centralPanel == null) {
			centralPanel = new JTabbedPane();
			centralPanel.add("Arcs view", getArcsMiddleHoverPanel());
			centralPanel.add("Suggestion list", getSuggestionListPanel());

			centralPanel.addChangeListener(new ChangeListener() {
				public void stateChanged(ChangeEvent e) {
					if (centralPanel.getSelectedIndex() == 0) {
						getArcRenderer().setHideArcs(false);
						updateActions();
					} else if (centralPanel.getSelectedIndex() == 1) {
						getArcRenderer().setHideArcs(true);
						updateActions();
					}
				}
			});

			centralPanel.addComponentListener(new ComponentAdapter() {
				public void componentResized(ComponentEvent e) {
					resizeLayers();
				}
			});
		}

		return centralPanel;
	}

	protected JPanel getArcsMiddleHoverPanel() {
		if (arcsMiddleHoverPanel == null) {
			arcsMiddleHoverPanel = new JPanel();
			arcsMiddleHoverPanel.setPreferredSize(new Dimension(250, 500));
			arcsMiddleHoverPanel.addMouseListener(mappingMouseListener);
			arcsMiddleHoverPanel.addMouseMotionListener(mappingMouseListener);
			arcsMiddleHoverPanel.addMouseWheelListener(mappingMouseListener);

			arcsMiddleHoverPanel.addComponentListener(new ComponentAdapter() {
				public void componentResized(ComponentEvent e) {
					resizeLayers();
				}
			});
		}
		return arcsMiddleHoverPanel;
	}
	
	private JPanel getCompletedMappingsPanel() {
		if(completedMappingsPanel == null) {
			completedMappingsPanel = new CompletedMappingsPane();
			
			// add a selection listener to sync completed mapping selection with tree selection
			completedMappingsPanel.getMappingsTable().getSelectionModel().addListSelectionListener(new ListSelectionListener() {
				public void valueChanged(ListSelectionEvent e) {
					handleCompletedMappingSelection();
				}
			});
			
			// add a click listener to sync completed mapping selection with tree selection
			completedMappingsPanel.getMappingsTable().addMouseListener(new MouseAdapter() {
				public void mousePressed(MouseEvent e) {
					handleCompletedMappingSelection();
				}
			});
		}
		
		return completedMappingsPanel;
	}
	
	private JPanel getSuggestionListPanel() {
		if (suggestionListPanel == null) {
			suggestionListPanel = new JPanel(new BorderLayout());

			suggestionListPanel.add(TabComponent.getTodoPane());

			// add a selection listener to sync suggestion selection with tree selection
			SelectableContainer container = MapPluginFacade.getSelectableContainer(MapPluginFacade.UI_UTILITY_SUGGESTION);
			container.addSelectionListener(new SelectionListener() {
				public void selectionPerformed(Object selectedItem) {
					handleSuggestionSelection(selectedItem);
				}
			});
			
			suggestionListPane = ((SuggestionListPane)TabComponent.getTodoPane()).getTodoList();
			
			// add a click listener to sync suggestion selection with tree selection
			suggestionListPane.getSuggestionTable().addMouseListener(new MouseAdapter() {
				public void mousePressed(MouseEvent e) {
					edu.stanford.smi.protegex.prompt.actionLists.Action action = suggestionListPane.getFirstSelection();
					handleSuggestionSelection(action);
				}
			});
			//suggestionListPane.addSelectionListener();
		}

		return suggestionListPanel;
	}

	private JPanel createTitlePanel(JLabel label, JTree tree, JPanel progressPanel) {
		JPanel panel = new JPanel(new BorderLayout());
		
		if(DataLoaderFacade.getInstance().getSuggestionsManager().getAllSuggestions().size() > 0) {
			panel.add(progressPanel, BorderLayout.WEST);
		}
		JToolBar toolbar = new JToolBar();
		toolbar.setFloatable(false);

		toolbar.add((tree == sourceTree ? sourceFilterButton : targetFilterButton));
		toolbar.add((tree == sourceTree ? sourceMappingMoveDownAction : targetMappingMoveDownAction));
		toolbar.add((tree == sourceTree ? sourceMappingMoveUpAction : targetMappingMoveUpAction));

		panel.add(toolbar, BorderLayout.CENTER);

		return panel;
	}

	private JLabel getSourceTitle() {
		if (sourceTitle == null) {
			sourceTitle = new JLabel(sourceKb.getProject().getName());
			sourceTitle.setFont(sourceTitle.getFont().deriveFont(Font.BOLD, sourceTitle.getFont().getSize2D() + 3f));
			sourceTitle.setForeground(new Color(0, 0, 128));
		}
		return sourceTitle;
	}

	private JLabel getTargetTitle() {
		if (targetTitle == null) {
			targetTitle = new JLabel(targetKb.getProject().getName());
			targetTitle.setFont(targetTitle.getFont().deriveFont(Font.BOLD, targetTitle.getFont().getSize2D() + 3f));
			targetTitle.setForeground(new Color(0, 0, 128));
		}
		return targetTitle;
	}

	protected BalloonPanel getTooltip() {
		return tooltip;
	}

	protected ArcRenderer getArcRenderer() {
		return arcRenderer;
	}

	public JPanel getArcsPanel() {
		return arcRenderer.getArcsPanel();
	}

	private JPanel getToolTipsPanel() {
		if (tooltipsPanel == null) {
			tooltipsPanel = new JPanel(null);
			tooltipsPanel.setOpaque(false);
			tooltip = new BalloonPanel("Sample tooltip");
			tooltip.setLocation(0, 0);
			tooltip.setVisible(false);
			tooltipsPanel.add(tooltip);
		}
		return tooltipsPanel;
	}

	protected MappingJTree getSourceTree() {
		if (sourceTree == null) {
			sourceTree = new MappingJTree(this, sourceKb.getRootCls(), mappingCollection.getSourceFrames(), sourceKb);
			sourceTree.addMouseListener(mappingMouseListener); // handles dragging
			sourceTree.addMouseMotionListener(mappingMouseListener); // handles dragging
			sourceTree.addTreeSelectionListener(this);
		}
		return sourceTree;
	}

	protected MappingJTree getTargetTree() {
		if (targetTree == null) {
			targetTree = new MappingJTree(this, targetKb.getRootCls(), mappingCollection.getTargetFrames(), targetKb);
			targetTree.addMouseListener(mappingMouseListener); // handles dragging
			targetTree.addMouseMotionListener(mappingMouseListener); // handles dragging
			targetTree.addTreeSelectionListener(this);
		}
		return targetTree;
	}

	private JPanel getMiddlePanel() {
		if (middlePanel == null) {
			middlePanel = new JPanel(new BorderLayout());

			JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
			JToolBar toolbar = new JToolBar();
			toolbar.setFloatable(false);
			toolbar.add(showMappingsAction);
			toolbar.addSeparator();
			toolbar.add(showOnlyExactMatchesAction);
			toolbar.add(filterToMappedItemsAction);
			toolbar.add(filterBackgroundItemsAction);
			toolbar.addSeparator();
			toolbar.add(addMappingAction);
			toolbar.add(addTemporaryMappingAction);
			toolbar.add(removeMappingAction);
			toolbar.addSeparator();
			toolbar.add(approveMappingAction);
			toolbar.add(temporaryMappingAction);
			panel.add(toolbar);
			middlePanel.add(panel, BorderLayout.NORTH);
		}
		return middlePanel;
	}

	private JTextField getSourceSearchText() {
		if (sourceSearchText == null) {
			sourceSearchText = new SearchTextField(this, getSourceTree(), sourceKb, TreeFilterManager.SOURCE_SEARCH);
		}

		return sourceSearchText;
	}

	private JTextField getTargetSearchText() {
		if (targetSearchText == null) {
			targetSearchText = new SearchTextField(this, getTargetTree(), targetKb, TreeFilterManager.TARGET_SEARCH);
		}

		return targetSearchText;
	}
}
